"""
功能：实现一个Python版本的飞机大战游戏
日期：2025/03/22
作者：张翠山
"""

import pygame
import random
import sys
import math

# 定义一些常量
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
FLY_WIDTH = 64
FLY_HEIGHT = 64
BULLET_WIDTH = 32
BULLET_HEIGHT = 32

is_game_over = False

# 定义一些公共变量
number_of_enemies = 6  # 敌机数量
list_of_enemies = []  # 敌机列表
list_of_bullets = []  # 子弹列表


# 定义一个英雄飞机类
class HeroFly(object):

    # 构造函数
    def __init__(self):
        # 计算英雄飞机出现的位置，水平方向居中
        self.x = (SCREEN_WIDTH - FLY_WIDTH) / 2
        self.y = SCREEN_HEIGHT - FLY_HEIGHT - 10

        # 英雄飞机的速度，正数则往右移动，负数则往左边移动
        self.speed = 0

        # 飞机背景图，构造函数加载一次就行了
        self.fly_img = pygame.image.load('./resource/player.png')

    # 英雄飞机自带的在画布展示自己的函数
    def show_hero_fly(self, screen):
        """
        :param screen: 窗口对象，直接传递到方法里面来
        :return:
        """
        screen.blit(self.fly_img, (self.x, self.y))
        self.x += self.speed

        # 边界判断，防止飞机出边界
        if self.x < 0:
            self.x = 0
        if self.x > SCREEN_WIDTH - FLY_WIDTH:
            self.x = SCREEN_WIDTH - FLY_WIDTH

    def move(self, speed):
        self.speed = speed


# 定义一个敌机类
class EnemyFly(object):

    # 构造函数
    def __init__(self):
        # 计算敌机出现的位置，水平方向随机出现
        self.x = random.randint(0, SCREEN_WIDTH - FLY_WIDTH)
        self.y = 10

        # 敌机的速度，正数则往右移动，负数则往左边移动，初始化的方向向右
        self.speed = 2

        # 飞机背景图，构造函数加载一次就行了
        self.fly_img = pygame.image.load('./resource/enemy.png')

    # 敌机自带的在画布展示自己的函数
    def show_enemy_fly(self, screen, hero_fly, sound):
        """
        :param screen: 窗口对象，直接传递到方法里面来
        :return:
        """
        screen.blit(self.fly_img, (self.x, self.y))
        self.x += self.speed

        # 边界判断，遇到边界的时候，速度取反，位置下沉
        if self.x < 0 or self.x > SCREEN_WIDTH - FLY_WIDTH:
            self.speed = -self.speed
            self.y += FLY_HEIGHT

        # 边界判断，如果敌机垂直方向已经和飞机重叠，则游戏结束
        if self.y > hero_fly.y - FLY_HEIGHT:
            # 敌机已经和我方飞机发生碰撞，游戏结束
            # print("Game OVER!!!")
            sound.fail()
            global is_game_over
            is_game_over = True

    # 敌机重置方法，当敌机被击中的时候，重置一下敌机的位置
    def reset(self):
        self.x = random.randint(0, SCREEN_WIDTH - FLY_WIDTH)
        self.y = 10
        self.speed = 2


# 定义一个子弹类
class Bullet(object):

    # 初始化的时候就给出子弹的位置
    def __init__(self, x, y):
        self.x = x
        self.y = y
        # 子弹背景图，构造函数加载一次就行了
        self.fly_img = pygame.image.load('./resource/bullet.png')

    # 显示子弹额位置
    def show_bullet(self, screen, sound, score):
        """
        :param screen: 窗口对象，直接传递到方法里面来
        :return:
        """
        screen.blit(self.fly_img, (self.x, self.y))
        self.y -= 5

        # 边界判断，遇到边界的时候，子弹从列表中删除
        if self.x < 0:
            list_of_bullets.remove(self)

        # 碰撞检测，检测该子弹是否与敌机发声了碰撞
        for enemy in list_of_enemies:
            # 先判断x轴坐标是否碰撞
            if (enemy.x - BULLET_WIDTH <= self.x <= enemy.x + FLY_WIDTH) and (
                    enemy.y <= self.y <= enemy.y + FLY_HEIGHT):
                # print("发声碰撞")
                sound.hit()  # 播放击中的声音
                list_of_bullets.remove(self)  # 子弹移除，敌机重置
                enemy.reset()
                score.score += 1
                break


# 音乐类
class Sound(object):
    # 构造函数
    def __init__(self):
        # 定义两个变量，一个是背景音乐，另一个是爆炸声音
        pygame.mixer.music.load('./resource/bg.wav')
        pygame.mixer.music.play(-1)  # 单曲循环

        self.hit_sound = pygame.mixer.Sound('./resource/exp.wav')
        self.fail_sound = pygame.mixer.Sound('./resource/laser.wav')

    # 击中敌机的时候播放声音
    def hit(self):
        self.hit_sound.play()

    # 敌机已经和我方飞机发生碰撞的时候播放的声音
    def fail(self):
        self.fail_sound.play()


# 分数类，封装分数值，以及分数的显示样式
class Score(object):
    def __init__(self):
        self.score = 0

    def show_score(self, screen):
        text = f"Score: {self.score}"
        font = pygame.font.Font('freesansbold.ttf', 32)
        score_render = font.render(text, True, (0, 255, 0))
        screen.blit(score_render, (10, 10))

    def show_game_over(self, screen):
        # 使用pygame计时和正弦函数来创建闪烁效果
        text = f"Game Over"
        font = pygame.font.Font('freesansbold.ttf', 64)
        # 根据当前时间调整透明度
        alpha = int(abs(255 * (math.sin(pygame.time.get_ticks() / 500))))
        red_color = (255, 0, 0)
        # 创建文本表面
        text_render = font.render(text, True, red_color)
        # 设置透明度
        text_render.set_alpha(alpha)
        screen.blit(text_render, (SCREEN_WIDTH / 2 - 200, SCREEN_HEIGHT / 2))


def main():
    # 初始化一个窗体
    pygame.init()

    # 设置默认窗体大小
    screen = pygame.display.set_mode((800, 600))

    # 设置窗体标题
    pygame.display.set_caption("飞机大战游戏")

    # 添加一个背景图片
    background = pygame.image.load("./resource/bg.png")

    # 创建一个英雄飞机对象
    hero_fly = HeroFly()

    # 初始化敌机列表，每个敌机是一个对象
    for i in range(0, number_of_enemies):
        list_of_enemies.append(EnemyFly())

    # 创建一个音乐对象，构造函数默认播放背景因为
    sound = Sound()

    # 创建一个分数对象，计分以及显示分数值
    score = Score()

    # 循环体，卡住窗口
    while True:
        event_list = pygame.event.get()
        for event in event_list:
            if event.type == pygame.QUIT:
                # 退出
                pygame.quit()
                sys.exit()

            # 对整个的游戏的事件处理在for循环里面处理，当用户挡住键盘事件的时候
            elif event.type == pygame.KEYDOWN and is_game_over == False:
                if event.key == pygame.K_LEFT or event.key == pygame.K_a:
                    hero_fly.move(-2)
                if event.key == pygame.K_RIGHT or event.key == pygame.K_d:
                    hero_fly.move(2)
                if event.key == pygame.K_SPACE:
                    list_of_bullets.append(Bullet(hero_fly.x + FLY_WIDTH / 2 - FLY_WIDTH / 4, hero_fly.y - 10))

        if is_game_over == False:
            # 绘制背景图片
            screen.blit(background, (0, 0))

            # 显示英雄飞机
            hero_fly.show_hero_fly(screen)

            # 显示敌机，初始化敌机列表，每个敌机是一个对象
            for enemy in list_of_enemies:
                enemy.show_enemy_fly(screen, hero_fly, sound)

            # 显示子弹
            for bullet in list_of_bullets:
                bullet.show_bullet(screen, sound, score)

        # 显示当前分数
        score.show_score(screen)

        # 显示是否游戏结束
        if is_game_over:
            score.show_game_over(screen)

        # 刷新屏幕
        pygame.display.update()


if __name__ == '__main__':
    main()
